use chrono::{DateTime, Local};
use commands::caculate_sha1;
use commands::delete_files;
use commands::get_datas;
use commands::get_progress_percent;
use commands::get_walkfile_count;

use image::DynamicImage;
use image::GenericImageView;
use rbatis::{crud, impl_select, RBatis};
use rbdc_sqlite::SqliteDriver;
use serde::{Deserialize, Serialize};
use sha1::Digest;
use sha1::Sha1;
use std::env;
use std::fs::create_dir;
use std::sync::atomic::Ordering::Relaxed;
use std::{
    error::Error,
    fs,
    sync::{atomic::AtomicUsize, Mutex, OnceLock},
};
mod commands;
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_dialog::init())
        .setup(|_| init())
        .invoke_handler(tauri::generate_handler![
            greet,
            get_datas,
            caculate_sha1,
            delete_files,
            get_progress_percent,
            get_walkfile_count
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

fn init() -> Result<(), Box<dyn Error>> {
    tokio::runtime::Runtime::new().unwrap().block_on(async {
        // 系统临时文件夹
        let mut file_path = env::temp_dir();
        file_path.push("quchong");
        file_path.push("photos");
        if !file_path.exists() {
            create_dir(&file_path).unwrap();
        }
        file_path.push("datas.db");
        let rb = RBatis::new();
        rb.init(
            SqliteDriver {},
            &format!("sqlite://{}", file_path.to_str().unwrap()),
        )
        .unwrap();

        rb.query(
            r#"
CREATE TABLE IF NOT EXISTS file_infos (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  path TEXT NOT NULL UNIQUE,
  len INTEGER NOT NULL,
  created_time TEXT NOT NULL,
  modified_time TEXT NOT NULL,
  sha1 TEXT,
  colortype INTEGER,
  exif INTEGER
);"#,
            vec![],
        )
        .await
        .unwrap();

        POOL.set(rb).unwrap();
        Ok(())
    })
}

static COUNT: AtomicUsize = AtomicUsize::new(0);

static PROGRESS_PERCENT: Mutex<f64> = Mutex::new(0.0);

pub static POOL: OnceLock<RBatis> = OnceLock::new();

#[derive(Serialize, Deserialize, Clone)]
struct FileInfos {
    id: Option<i64>,
    path: String,
    len: u64,
    created_time: String,
    modified_time: String,
    sha1: Option<String>,
    colortype: Option<u8>,
    exif: Option<usize>,
}
crud!(FileInfos {});
impl_select!(FileInfos {select_all_by_path(path:&str)=>"`where path like #{path}||'%'`"});

async fn clear_not_exists_file_info() {
    let file_infos = FileInfos::select_all(POOL.get().unwrap()).await.unwrap();
    for i in file_infos {
        let fs = fs::File::open(i.path);
        match fs {
            Ok(_) => {}
            Err(_) => {
                FileInfos::delete_by_column(POOL.get().unwrap(), "id", i.id)
                    .await
                    .unwrap();
            }
        }
    }
}

async fn walk_dir(path: &str) {
    for entry in fs::read_dir(path).unwrap() {
        COUNT.fetch_add(1, Relaxed);
        let entry = entry.unwrap();
        if !entry.path().is_dir() {
            let path = entry.path().to_str().unwrap().to_string();

            let create_time: DateTime<Local> = entry.metadata().unwrap().created().unwrap().into();
            let modified_time: DateTime<Local> =
                entry.metadata().unwrap().modified().unwrap().into();

            let mut file_info = FileInfos {
                id: None,
                path: path.to_string(),
                len: entry.metadata().unwrap().len(),
                created_time: create_time.format("%Y-%m-%d %H:%M:%S").to_string(),
                modified_time: modified_time.format("%Y-%m-%d %H:%M:%S").to_string(),
                sha1: None,
                colortype: None,
                exif: None,
            };
            let select_value = POOL
                .get()
                .unwrap()
                .query_decode::<FileInfos>(
                    r#"
SELECT
    *
FROM
    file_infos
WHERE
    path = ?
LIMIT
    1;"#,
                    vec![rbs::Value::String(
                        entry.path().to_str().unwrap().to_string(),
                    )],
                )
                .await;

            match select_value {
                Ok(value) => {
                    if value.created_time != file_info.created_time
                        || value.modified_time != file_info.modified_time
                    {
                        file_info.id = value.id;
                        FileInfos::update_by_column_skip(
                            POOL.get().unwrap(),
                            &file_info,
                            "id",
                            false,
                        )
                        .await
                        .unwrap();
                    }
                }
                Err(_) => {
                    FileInfos::insert(POOL.get().unwrap(), &file_info)
                        .await
                        .unwrap();
                }
            }
        } else {
            Box::pin(walk_dir(entry.path().to_str().unwrap())).await;
        }
    }
}

fn caculate_px_sha1(image: &DynamicImage) -> String {
    let mut vec = Vec::new();
    // 遍历图片的每个像素
    for y in 0..image.height() {
        for x in 0..image.width() {
            // 获取像素的RGBA值
            let pixel = image.get_pixel(x, y);
            let r = pixel[0];
            let g = pixel[1];
            let b = pixel[2];
            let a = pixel[3];
            vec.push(r);
            vec.push(g);
            vec.push(b);
            vec.push(a);
            // 处理像素值...
        }
    }
    let mut hasher = Sha1::new();

    // process input message
    hasher.update(vec);

    // acquire hash digest in the form of GenericArray,
    // which in this case is equivalent to [u8; 20]
    format!("{:x}", hasher.finalize())
}
